﻿using System;
using System.Collections.Generic;
using DirectMidi;
using Oxage.Common;
using Oxage.Forms;
using System.Windows.Forms;

namespace Oxage.Midi
{
	public class DirectMidiConnector : IConnector
	{
		#region Members
		//private CDMReceiver Receiver = new CDMReceiver();
		private CDirectMusic CDMusic = new CDirectMusic();
		private CInputPort CInPort = new CInputPort();
		private COutputPort COutPort = new COutputPort();
		private CDLSLoader CLoader = new CDLSLoader();
		private CCollection CCollection;
		private CInstrument CInstrument;
		private CSampleInstrument CSample = new CSampleInstrument();
		private DirectMidiForm panel;
		private bool isOpened = false;
		#endregion

		#region Constructor
		public DirectMidiConnector()
		{
			panel = new DirectMidiForm(this);
		}
		#endregion

		#region Methods
		public void ProgramChange(object instrument)
		{
			if (this.IsOpened)
			{
				try
				{
					// Unloads the instruments from the port
					COutPort.UnloadInstrument(CInstrument);

					// Gets the instrument with index 214 from the CollectionB
					CCollection.GetInstrument(out CInstrument, instrument as INSTRUMENTINFO);

					// Assigns it to the MIDI program 0
					CInstrument.SetPatch(0);
					CInstrument.SetNoteRange(0, 127);

					// Downloads the instruments to the output ports
					COutPort.DownloadInstrument(CInstrument);
				}
				catch
				{
				}
			}
		}

		public void LoadDLS(string filename)
		{
			if (this.IsOpened)
			{
				try
				{
					// Unloads the collections from the loader
					CLoader.UnloadCollection(CCollection);

					// Loads the default GM collection of the software synthesizer
					CLoader.LoadDLS(filename, out CCollection);

					//TODO: Select first instrument

					panel.LoadInstruments();
				}
				catch
				{
				}
			}
		}

		private List<DeviceInfo> deviceList = new List<DeviceInfo>();
		public IEnumerable<DeviceInfo> GetDevices()
		{
			try
			{
				if (deviceList == null || deviceList.Count == 0)
				{
					deviceList = new List<DeviceInfo>();

					//CDMusic.Initialize();
					//COutPort.Initialize(CDMusic);
					//CInPort.Initialize(CDMusic);

					INFOPORT PortInfo;
					UInt32 dwPortCount = 0;

					// Software Synthesizer selection
					do
					{
						if (COutPort.GetPortInfo(++dwPortCount, out PortInfo) == 1)
						{
							break;
						}
						deviceList.Add(new DeviceInfo() { PortInfo = PortInfo, Name = PortInfo.szPortDescription });
					}
					while (PortInfo.dwFlags != DMUS_PC.XGINHARDWARE);
				}
			}
			catch
			{
			}

			return deviceList;
		}

		public void ProgramChange(int instrument)
		{
			if (this.IsOpened)
			{
				// Unloads the instruments from the port
				COutPort.UnloadInstrument(CInstrument);

				// Gets the instrument with index 214 from the CollectionB
				CCollection.GetInstrument(out CInstrument, instrument);

				// Assigns it to the MIDI program 0
				CInstrument.SetPatch(0);
				CInstrument.SetNoteRange(0, 127);

				// Downloads the instruments to the output ports
				COutPort.DownloadInstrument(CInstrument);
			}
		}

		private List<MidiProgram> programList = new List<MidiProgram>();
		public IEnumerable<MidiProgram> GetProgramList()
		{
			return this.programList;
		}

		public override string ToString()
		{
			return "DirectMidi";
		}
		#endregion

		#region IConnector Members
		public bool IsOpened
		{
			get
			{
				return this.isOpened;
			}
			set
			{
				this.isOpened = value;
				this.panel.IsLocked = value;
			}
		}

		public DataDirection Direction
		{
			get { return DataDirection.Output; }
		}

		public void Open()
		{
			Open(null);
		}

		public void Open(INFOPORT PortInfo)
		{
			if (!this.IsOpened)
			{
				try
				{
					//If PortInfo is set then user has requested a device change
					bool requestDeviceChange = (PortInfo != null);

					CDMusic.Initialize();
					COutPort.Initialize(CDMusic);
					//CInPort.Initialize(CDMusic);

					//INFOPORT PortInfo;
					UInt32 dwPortCount = 0;

					// Software Synthesizer selection
					if (PortInfo == null)
					{
						do
						{
							COutPort.GetPortInfo(++dwPortCount, out PortInfo);
						}
						while (0 == (PortInfo.dwFlags & DMUS_PC.SOFTWARESYNTH));
					}

					// Output port activation given the port information 
					COutPort.SetPortParams(0, 0, 1, SET.REVERB | SET.CHORUS, 44100);
					COutPort.ActivatePort(PortInfo);

					// Initialize the Loader object  
					CLoader.Initialize();

					// Loads the default GM collection of the software synthesizer
					CLoader.LoadDLS(null, out CCollection);

					
					#region Load instruments
					// Structure of the instrument information
					INSTRUMENTINFO InstInfo;
					UInt32 dwInstIndex = 0;

					this.programList = new List<MidiProgram>();

					// Enumerates instruments in CollectionB
					while ((HR_DMUS)CCollection.EnumInstrument(dwInstIndex++, out InstInfo) == HR_DMUS.HR_S_OK)
					{
						this.programList.Add(new MidiProgram() { Number = (int)InstInfo.dwPatchInCollection, Name = InstInfo.szInstName, Instrument = InstInfo });
						//Console.WriteLine("Instrument name: " + InstInfo.szInstName);
						//Console.WriteLine("Patch in collection: " + InstInfo.dwPatchInCollection);
						//Console.WriteLine("----------------------------------------");
					}

					// Gets the instrument with index 214 from the CollectionB
					CCollection.GetInstrument(out CInstrument, 1);

					// Assigns it to the MIDI program 0
					CInstrument.SetPatch(0);
					CInstrument.SetNoteRange(0, 127);

					try
					{
						// Downloads the instruments to the output ports
						COutPort.DownloadInstrument(CInstrument);

						COutPort.SendMidiMsg(COutputPort.EncodeMidiMsg(DirectMidi.MidiMessage.PATCH_CHANGE, 0, 0, 0), 0);
					}
					catch
					{
						//Some devices (like virtual midi through cable) do not support instruments
					}
					#endregion


					//Update GUI
					panel.LoadInstruments();

					if (!requestDeviceChange)
					{
						//Do this only for the first time
						panel.LoadDevices();
					}

					this.IsOpened = true;

					//Default collection
					LoadDLS(null);
				}
				catch (Exception ex)
				{
					MessageBox.Show("DirectMidi: " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
					this.IsOpened = false;
				}
			}
			panel.RefreshControls();
		}

		public void Close()
		{
			try
			{
				// Unloads the collections from the loader
				//CLoader.UnloadCollection(CCollectionA);
				CLoader.UnloadCollection(CCollection);

				// Unloads the instruments from the port
				COutPort.UnloadInstrument(CInstrument);
				//COutPort.UnloadInstrument(CInstrument2);

				//// Unloads the sample instruments
				//COutPort.UnloadInstrument(CSample1);
				//COutPort.UnloadInstrument(CSample2);

				//// Frees allocated memory
				//COutPort.DeallocateMemory(CSample1);
				//COutPort.DeallocateMemory(CSample2);

				COutPort.ReleasePort();
				CInPort.ReleasePort();
			}
			catch
			{
			}

			this.IsOpened = false;
			panel.RefreshControls();
		}

		public void Send(IMessage message)
		{
			if (this.IsOpened && message is MidiMessage)
			{
				uint e = 0;
				var midi = message as MidiMessage;
				switch (midi.Event)
				{
					case MidiEvents.NoteOn:
						e = COutputPort.EncodeMidiMsg(DirectMidi.MidiMessage.NOTE_ON, midi.Channel, midi.Note, midi.Velocity);
						break;

					case MidiEvents.NoteOff:
						e = COutputPort.EncodeMidiMsg(DirectMidi.MidiMessage.NOTE_OFF, midi.Channel, midi.Note, midi.Velocity);
						break;
				}
				if (e > 0)
				{
					COutPort.SendMidiMsg(e, 0);
				}
			}
		}

		public event EventHandler<EventArgs<IMessage>> Received;
		protected virtual void OnReceived(IMessage message)
		{
			if (Received != null)
			{
				Received(this, new EventArgs<IMessage>(message));
			}
		}

		public IConfigPanel GetConfigPanel()
		{
			return panel;
		}
		#endregion
	}

	public class DeviceInfo
	{
		/// <summary>
		/// Gets or sets a human friendly name of the device.
		/// </summary>
		public string Name
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets the handle and device info.
		/// </summary>
		public INFOPORT PortInfo
		{
			get;
			set;
		}

		/// <summary>
		/// Displays human friendly name in ComboBox.
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return this.Name;
		}
	}
}
